/**
 * new 操作符的底层操作
 * 主要是执行一个构造函数，返回一个实例对象
 * 
 * 创建一个新对象；
 * 将构造函数的作用域赋给新对象（this 指向新对象）；
 * 执行构造函数中的代码（为这个新对象添加属性）；
 * 返回新对象。
 * */ 

function Person(){
  this.name = 'Jack'
}
let p = new Person()
//console.log(p.name)


/**
 * call、apply、bind 底层逻辑，必须是绑定函数使用
 * 都可以改变函数 func 的 this 指向。
 * call 和 apply 的区别在于，传参的写法不同：apply 的第 2 个参数为数组； call 则是从第 2 个至第 N 个都是给 func 的传参；
 * 而 bind 和这两个（call、apply）又不同，bind 虽然改变了 func 的 this 指向，但不是马上执行，而这两个（call、apply）是在改变了函数的 this 指向之后立马执行
 * */ 

let a = {
  name: 'jack',
  getName: function (msg) {
    return msg + this.name;
  }
}

let b = {
  name: 'lily'
}
// console.log(a.getName('hello~'));  // hello~jack
// console.log(a.getName.call(b, 'hi~'));  // hi~lily
// console.log(a.getName.apply(b, ['hi~']))  // hi~lily
// let name = a.getName.bind(b, 'hello~');
// console.log(name());  // hello~lily

// 类似于实现数据里类型的检查
function getType(obj){
  let type = typeof obj
  if(type!=="object") {
    return type
  }

  return Object.prototype.toString.call(obj).replace(/^s/,'s1')
}
let _name = function () { return 123 }      // 123 number '123' string  [1,2,3] [object Array] {name: 123} [object Object] function(){return 123} function
// console.log(getType(_name))

/**
 * new 函数的实现
 * 让实例可以访问到私有属性；
 * 让实例可以访问构造函数原型（constructor.prototype）所在原型链上的属性；
 * 构造函数返回的最后结果是引用数据类型。
 * */ 

function _new(Fun,...args){
  if (typeof Fun !=='function'){
    throw 'Fun must be a function';
  }
  let obj = new Object()
  obj.__proto__ = Object.create(Fun.prototype)
  
  let res = Fun.apply(obj, [...args])

  let isObject = typeof res ==="object" && res!==null
  let isFunction = typeof res === "function"
  return isObject || isFunction ? res : obj 
}


/**
 * call、apply
 * */ 
Function.prototype.call = function (context, ...args) {

  var context = context || window;

  context.fn = this;

  var result = eval('context.fn(...args)');

  delete context.fn

  return result;

}

Function.prototype.apply = function (context, args) {

  let context = context || window;

  context.fn = this;

  let result = eval('context.fn(...args)');

  delete context.fn

  return result;

}

/**
 * bind
 * */ 
Function.prototype.bind = function (context, ...args) {

  if (typeof this !== "function") {

    throw new Error("this must be a function");

  }

  var self = this;

  var fbound = function () {

    self.apply(this instanceof self ? this : context, args.concat(Array.prototype.slice.call(arguments)));

  }

  if (this.prototype) {

    fbound.prototype = Object.create(this.prototype);

  }

  return fbound;

}
